package com.three.common.router;

import com.google.common.eventbus.Subscribe;
import com.three.api.connection.Connection;
import com.three.api.connection.SessionContext;
import com.three.api.event.ConnectionCloseEvent;
import com.three.api.router.ClientLocation;
import com.three.api.router.RouterManager;
import com.three.api.spi.common.CacheManager;
import com.three.api.spi.common.CacheManagerFactory;
import com.three.common.CacheKeys;
import com.three.event.EventConsumer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 远程路由管理类
 * Created by mathua on 2017/5/25.
 */
@Component
public class RemoteRouterManager extends EventConsumer implements RouterManager<RemoteRouter> {
    public static final Logger LOGGER = LoggerFactory.getLogger(RemoteRouterManager.class);

    private final CacheManager cacheManager = CacheManagerFactory.create();

    @Override
    public RemoteRouter register(long playerId, RemoteRouter router) {
        String key = CacheKeys.getPlayerRouteKey(playerId);
        String field = Integer.toString(router.getRouteValue().getClientType());
        ClientLocation old = cacheManager.hget(key, field, ClientLocation.class);
        cacheManager.hset(key, field, router.getRouteValue());
        LOGGER.info("register remote router success playerId={}, newRouter={}, oldRoute={}", playerId, router, old);
        return old == null ? null : new RemoteRouter(old);
    }

    /**
     * 目前的实现方式是非原子操作(get:set)，可能会有并发问题，虽然概率很低
     * 后续考虑采用lua脚本，实现原子操作
     *
     * @param playerId     用户ID
     * @param clientType 客户端类型
     * @return 删除路由是否成功
     */
    @Override
    public boolean unRegister(long playerId, int clientType) {
        String key = CacheKeys.getPlayerRouteKey(playerId);
        String field = Integer.toString(clientType);
        ClientLocation location = cacheManager.hget(key, field, ClientLocation.class);
        if (location == null || location.isOffline()) return true;
        cacheManager.hset(key, field, location.offline());
        LOGGER.info("unRegister remote router success playerId={}, route={}", playerId, location);
        return true;
    }

    @Override
    public Set<RemoteRouter> lookupAll(long playerId) {
        String key = CacheKeys.getPlayerRouteKey(playerId);
        Map<String, ClientLocation> values = cacheManager.hgetAll(key, ClientLocation.class);
        if (values == null || values.isEmpty()) return Collections.emptySet();
        return values.values().stream().map(RemoteRouter::new).collect(Collectors.toSet());
    }

    @Override
    public RemoteRouter lookup(long playerId, int clientType) {
        String key = CacheKeys.getPlayerRouteKey(playerId);
        String field = Integer.toString(clientType);
        ClientLocation location = cacheManager.hget(key, field, ClientLocation.class);
        LOGGER.info("lookup remote router playerId={}, router={}", playerId, location);
        return location == null ? null : new RemoteRouter(location);
    }

    /**
     * 监听链接关闭事件，清理失效的路由
     *
     * @param event
     */
    @Subscribe
    void on(ConnectionCloseEvent event) {
        Connection connection = event.connection;
        if (connection == null) return;
        SessionContext context = connection.getSessionContext();
        long playerId = context.getPlayerId();
        if (playerId == 0) return;
        String key = CacheKeys.getPlayerRouteKey(playerId);
        String field = Integer.toString(context.getClientType());
        ClientLocation location = cacheManager.hget(key, field, ClientLocation.class);
        if (location == null || location.isOffline()) return;

        String connId = connection.getId();
        //2.检测下，是否是同一个链接, 如果客户端重连，老的路由会被新的链接覆盖
        if (connId.equals(location.getConnId())) {
            cacheManager.hset(key, field, location.offline());
            LOGGER.info("clean disconnected remote route, playerId={}, route={}", playerId, location);
        } else {
            LOGGER.info("clean disconnected remote route, not clean:playerId={}, route={}", playerId, location);
        }
    }
}